#!/usr/bin/env python

""" vlan.py Virtual Lan for Emulating a Grid
 Basile Clout, August 2007

This script determines the routing tables of the nodes with respect to the grid configuration.
Usage: ./vlan.py [arguments]
-a, --add_conf	Add the rules (routes and other) to the nodes
-d, --del_conf	Delete these rules
-c, --conf_file	Grid configuration file
-v, --verbose	Display the summary
-h, --help	This help
-f, --fake	Just display the routes, dont send it
    --debug	Print some debug info and turn verbose (-v) on
-w, --wrekagrid	Display the equivalent wrekawrid configuration file

TODO:   make the config more logical: if no data (or 0), then don't add qdiscs or rules, just modify routing table
	make the wrekagrid config file consistant with that
	respect fl_type ... (improve fl_type in parse conf)
Information, help, questions, suggestions, whatever!:
basile.clout@unb.ca
"""


from string import *
from datetime import datetime
import os
import sys
import getopt
import subprocess
import pprint

class cluster:
    """ Define a cluster structure, a component of a grid """
    def __init__(self, name):
        """ Initialize an instance of cluster with the name of the cluster """
        
        self.name = name                # Name of the cluster
        self.nodes = []                 # List of the nodes in this cluster
        self.routers = []       	# List of the routers for this cluster
        self.adj = {} 			# Hash containing tuples (bandwidth, latency) with directly connected clusters
        self.bdw = 0                    # Bandwidth intra-cluster. If 0, do not change
        self.lat = 0                    # Latency intra-cluter. If  0, do not change

    def __repr__(self):
        """ Representiation of the cluster """

        sentence = "name = " + str(self.name) +\
                   "\ndirectly connected clusters = " + str(self.adj) +\
                   "\ncontaining the nodes " + str(self.nodes) +\
                   "\nAmong them, some are routers" + str(self.routers)+\
                   "\nbandwidth = " + str(self.bdw) +"MBits/s, latency = " + str(self.lat) + "ms." 
        
        return sentence
    

class node:
    """ Define a node structure, the component of a cluster """

    def __init__(self, name):

        self.master = ""                # Master node. Same for every node.
        self.ifn = ""                   # Interface. Here, same for every node
        self.name = name                # Unique identifier for the node
        self.address = "0.0.0.0"        # IP address of the node
        self.adj = []                   # List of external adjacent nodes (for a router)
        self.incluster = ""             # Name of the containing cluster
        
        self.add_topo = ""                 # Order for adding routes in the routing table
        self.del_topo = ""                 # Order for deleting routes in the routing table

        self.add_net = ""               # Order for adding net (tc) config
        self.del_net = ""               # Order for deleting net (tc) config
        

    def build_topo(self, nodes, clusters):

        forward = Template('echo $switch > /proc/sys/net/ipv4/ip_forward;\n')
        redirects = Template('echo $switch > /proc/sys/net/ipv4/conf/$myif/send_redirects;\n')
        flush = "ip route flush cache;\n"

        addr, delr = self.build_routes(nodes, clusters)
        self.add_topo += addr
        self.del_topo += delr
        
        if self.adj != []:              # If router
            self.add_topo += forward.substitute(switch = 1)
            self.add_topo += redirects.substitute(switch = 0, myif = "all")
            self.add_topo += redirects.substitute(switch = 0, myif = "eth0")
            self.add_topo += redirects.substitute(switch = 0, myif = "eth1")
            self.del_topo += forward.substitute(switch = 0)
            self.del_topo += redirects.substitute(switch = 1, myif = "all")
            self.del_topo += redirects.substitute(switch = 1, myif = "eth0")
            self.del_topo += redirects.substitute(switch = 1, myif = "eth1")

        self.add_topo += flush
        self.del_topo += flush
        

    def build_net(self, nodes, clusters):
        """ Add rules (iproute2 & tc) for policing and scheduling the
        network Diffence with wrekavoc: - more efficient algo, using
        the route filter instead of u32+ip dest - simpler algo: dont
        care about ingress (We suppose that both ends of the link
        schedule the connection in the same way
        """
        
        max_rate = "200000"             # Max rate = 200Mbits/s. Hope it is not going to limit anything ...
        del_net_root = "tc qdisc del dev " + self.ifn + " root &> /dev/null;\n"
        add_net_root = "tc qdisc add dev " + self.ifn + " root handle 1: htb;\n"

        add_net_class_bdw = Template('tc class add dev $ifn parent $parent classid $classid htb rate ${rate}kbit ceil ${rate}kbit mtu 1500;\n')
        add_net_filter_bdw = Template('tc filter add dev $ifn parent $parent protocol ip prio 100 route to $realm classid $classid;\n')
        add_net_qdisc_lat_prio = Template('tc qdisc add dev $ifn parent $parent handle $handle prio;\n')
        add_net_qdisc_lat_netem = Template('tc qdisc add dev $ifn parent $parent handle $handle netem latency ${latency}ms;\n')

        del_net = del_net_root

        add_net = del_net_root
        add_net = add_net_root

        # For all nodes
        (loc_bdw, loc_lat) = (clusters[self.incluster].bdw, clusters[self.incluster].lat)

        if loc_bdw != 0:
            add_net += add_net_class_bdw.substitute(ifn = self.ifn, parent = "1:", classid = "1:2", rate = loc_bdw)
        else:
            add_net += add_net_class_bdw.substitute(ifn = self.ifn, parent = "1:", classid = "1:2", rate = max_rate)
        add_net += add_net_filter_bdw.substitute(ifn = self.ifn, parent = "1:", realm = "local_" + self.incluster, classid = "1:2")

        if loc_lat != 0:
            add_net += add_net_qdisc_lat_prio.substitute(ifn = self.ifn, parent = "1:2", handle = "2:")
            add_net += add_net_qdisc_lat_netem.substitute(ifn = self.ifn, parent = "2:1", handle = "3:", latency = loc_lat)
            add_net += add_net_qdisc_lat_netem.substitute(ifn = self.ifn, parent = "2:2", handle = "4:", latency = loc_lat)
            add_net += add_net_qdisc_lat_netem.substitute(ifn = self.ifn, parent = "2:3", handle = "5:", latency = loc_lat)
                                
        # If this IS a router
        if self.adj != []:

            id_qdisc = 6                      # First available qdisc major number
            
            for _node in self.adj:

                dist_cluster = nodes[_node].incluster
                (dist_bdw, dist_lat) = clusters[self.incluster].adj[dist_cluster]
    
                if dist_bdw != 0:
                    add_net += add_net_class_bdw.substitute(ifn = self.ifn, parent = "1:", classid = "1:"+str(id_qdisc), rate = dist_bdw)
                else:
                    add_net += add_net_class_bdw.substitute(ifn = self.ifn, parent = "1:", classid = "1:"+str(id_qdisc), rate = max_rate)
                add_net += add_net_filter_bdw.substitute(ifn = self.ifn, parent = "1:", realm = "dist_" + dist_cluster, classid = "1:" + str(id_qdisc) )

                if dist_lat != 0:
                    add_net += add_net_qdisc_lat_prio.substitute(ifn = self.ifn, parent = "1:" + str(id_qdisc), handle = str(id_qdisc) + ":")
                    add_net += add_net_qdisc_lat_netem.substitute(ifn = self.ifn, parent = str(id_qdisc) + ":1", handle = str(id_qdisc+1) + ":", latency = dist_lat)
                    add_net += add_net_qdisc_lat_netem.substitute(ifn = self.ifn, parent = str(id_qdisc) + ":2", handle = str(id_qdisc+2) + ":", latency = dist_lat)
                    add_net += add_net_qdisc_lat_netem.substitute(ifn = self.ifn, parent = str(id_qdisc) + ":3", handle = str(id_qdisc+3) + ":", latency = dist_lat)

                id_qdisc += 4
                                
        self.add_net = add_net
        self.del_net = del_net

        
    
        
    def build_routes(self, nodes, clusters):
        """ Build routes to add and delete in the routing table for this node """

        id_realm = 1                    # id of the realm. The value 0, \"cosmos\", is already taken by the kernel
        
        name_realm_list = []
        
        add_routes = ""
        del_routes = ""

        t = Template('ip route $verb $dest via $gw realm $realm;\n')
        t_loc = Template('ip route $verb ${master}/32 dev $ifn proto kernel scope link src $me realm $realm;\n')
        
        # Save the rt_realms file and add the base indices:name pairs

        rt_realms_file = "/etc/iproute2/rt_realms" # We need to update this file to allow the binding int id -> string value
        rt_realms_file_bak = rt_realms_file + ".bak"         

        # Back up the original rt_realms file
        add_routes += "cp -f " + rt_realms_file + " " + rt_realms_file_bak + ";\n"

        t_realms = Template('echo "$id_realm\t$name_realm" >> $rt_realms_file;\n')

        realm_loc = "local_" + self.incluster
        name_realm_list.append(realm_loc)
        
        add_routes += t_realms.substitute(id_realm = str(id_realm), name_realm = realm_loc, rt_realms_file = rt_realms_file) # Add a value in the rt_realms file

        
        # Add the two common routes (local and master) with their corresponding realm
        # Add the master route
#        id_realm += 1
#       add_routes += t_master.substitute(verb = "add", master = self.master, ifn = self.ifn, me = self.address, realm = realm_master)
 #       add_routes += "ip route change 172.16.0.0/16 dev " + self.ifn + " proto kernel scope link src " + self.address + " realm " + realm_loc + ";\n"
        
        # When deleting routes, first get back to the original rt_realms file
        del_routes += "cp -f " + rt_realms_file_bak + " " + rt_realms_file + ";\n"

        # Then delete master route and return to the original local route
        # if you want to keep the old behavior (default:nothing, return to svn revision 67
#        del_routes += t_master.substitute(verb = "del", master = self.master, ifn = self.ifn, me = self.address, realm = "master")
#        del_routes += "ip route change 172.16.0.0/16 dev " + self.ifn + " proto kernel scope link src " + self.address + ";\n"
        
        id_realm += 1
        
        rip_table = rip(clusters, nodes)

        for _name, dest in nodes.iteritems():

            if _name == self.name:
                continue

            gate = self.find_gw(dest, nodes, clusters, rip_table.table)
            
            if gate != "":              # If there needs a route AND there is a corresponding gate

                if nodes[gate].incluster != self.incluster: # If the gateway is NOT in the same cluster, use a different realm
                    name_realm = "dist_"+str(dest.incluster)
                    if name_realm not in name_realm_list:
                        name_realm_list.append(name_realm)
                        add_routes += t_realms.substitute(id_realm = str(id_realm), name_realm =  name_realm, rt_realms_file = rt_realms_file)
                else:                   # If the gateway IS in the same cluster, use the local realm
                    name_realm = realm_loc

                add_routes += t.substitute(verb = "add", dest = dest.address, gw = nodes[gate].address, realm = name_realm)
                del_routes += t.substitute(verb = "del", dest = dest.address, gw = nodes[gate].address, realm = name_realm)
                id_realm += 1
                

            else:                       # If it is a directly connected node
                if dest.incluster != self.incluster: # If the destination node is not in the same cluster, use a new realm
                    name_realm = "dist_"+str(dest.incluster)
                    if name_realm not in name_realm_list:
                        name_realm_list.append(name_realm)
                        add_routes += t_realms.substitute(id_realm = str(id_realm), name_realm =  name_realm, rt_realms_file = rt_realms_file)
                else:                   # If the gateway IS in the same cluster, use the local realm
                    name_realm = realm_loc
                        
                add_routes += t_loc.substitute(verb = "add", master = dest.address, ifn = self.ifn, me = self.address, realm = name_realm)
                del_routes += t_loc.substitute(verb = "del", master = dest.address, ifn = self.ifn, me = self.address, realm = name_realm)
                
        return add_routes, del_routes




    def find_gw(self, dest, nodes, clusters, rip):
        """ Find gateway for this node to the destination node """

        if self.name == dest.name:      # This node is the current node
            pass
        elif self.incluster == dest.incluster or dest.name in self.adj: # This node is directly connected to the current node.
            pass
        
        elif self.adj == []:         # This node is NOT a router and the node is in another cluster
            return rip[self.incluster][dest.incluster][1]
        
        else:                           # Current node IS a router
            if self.name != rip[self.incluster][dest.incluster][1]: # If it is a router, but NOT connected to dest.incluster, it behaves like a minion
                return rip[self.incluster][dest.incluster][1]
            else:
                next_cluster = rip[self.incluster][dest.incluster][0] # Find the corresponding cluster
                return rip[next_cluster][self.incluster][1]           # Graph not directed
            
        return ""
            

    def send_order(self, log, cmd = 1, fl_type = 2):
        """ Send orders topology and/or network """
        
        t = Template("ssh $address \"$order\" ")
        string = ""
        
        if cmd==1:
            if fl_type == 0:
                string += self.del_topo
            elif fl_type == 1:
                string += self.del_net
            elif fl_type == 2:
                string += self.del_topo
                string += self.del_net
                
        elif cmd==0:
            if fl_type == 0:
                string += self.add_topo
            elif fl_type == 1:
                string += self.add_net
            elif fl_type == 2:
                string += self.add_topo
                string += self.add_net
        else:
            print "ERROR: Error order to send\n"
            
        order = t.substitute(address = self.address, order = string)

        print "Sending config to " + self.address + " ..."
        log.write("Sending config to " + self.address + " ...\n")
        print order
        log.write(order)

        stdin, stdout, stderr = os.popen3(order)
        output, errput = stdout.read(), stderr.read()

        if errput != "":
            log.writelines("\n" + errput)
            print "Error when sending config to %s (%s)\n\n" % (self.address, self.name)
            log.write("Error when sending config to %s (%s)\n\n" % (self.address, self.name))

        else:
            print "... OK!\n\n"
            log.write("\n ... OK!\n\n")


    def __repr__(self):

        sentence = "node " + self.name +\
                   "\n address " + str(self.address) + \
                   "\n in cluster " + str(self.incluster) +\
                   "\n connected nodes from external cluster (if router) = " + str(self.adj) +\
                   "\n Order add topo = " + str(self.add_topo) +\
                   "\n Order del topo = " + str(self.del_topo)

        return sentence
            


def conf_parse(conf):
    """ Parse a configuration file to fill up the clusters and nodes lists """

    index = 0         # Used for determining Unique name for each node
    new_cluster = 1         # 0 or 1: a blank line shows a new cluster
    nodes = {}                          # Hash of nodes
    clusters = {}                       # Hash of clusters
    mode = 0                            # Mode = 1 if we define the topology, mode = 2 if network (mode = 0 is invalid)
    mod = -1

    for line in conf:

        line.strip()                    # Strip spaces in line
        if line[0]=="#":                # Comment line
            pass

        elif line[0]=="@":              # Mode line
            if line[1:5] == "topo":
                mode = 1
                if mod == -1:
                    mod = 0
                else:
                    mod = 2
            elif line[1:8] == "network":
                mode = 2
                if mod == 0:
                    mod = 2
                else:
                    mod = 1
            else:
                print "ERROR: Bad mode: "+ line
                sys.exit(2)
                
        elif line == "\n":              # A line containing a "newline" is a blank line (after stripping)
            new_cluster = 1

        else:                           # The line defines a new cluster or a new node
            
            parts = line.split(":")
            # TODO: check format regex for clusters and nodes

            if mode == 1:

                if new_cluster == 1:        # The line defines a new cluster
                    
                    _cluster = cluster(parts[0].strip())
                    for l in parts[1].split(","):
                        _cluster.adj[l.strip()] = (0, 0) # Initialise the bdw and lat values for the adjacent cluster to (0, 0)
                        
                    clusters[_cluster.name] = _cluster
                    new_cluster = 0

                else:                       # This line defines a new node
                    options = parts[1].split(",")
                    
                    if options[0].strip() == "": # Default name if not specified
                        u_name = "m" + str(index) + str(_cluster.name)
                        _node = node(u_name)
                    else:
                        _node = node(options[0].strip())
                        
                    # TODO: Check regex of the address
                    _node.address = parts[0].strip()

                    if len(options[1:]) > 0: # If this node is a router
                        _cluster.routers.append(_node.name)
                    
                    _node.adj.extend([l.strip() for l in options[1:]])
                    _node.incluster = _cluster.name
                    
                    _cluster.nodes.append(_node.name)
                    nodes[_node.name] = _node

                    index += 1

            elif mode == 2:

                relation = parts[0].split("<->")
                options = parts[1].split()

                if len(options) != 2:
                    print "WARNING conf file. Please give 2 values for each network, bandwidth first (MBits/s), latency second (ms)"

                if len(relation) == 1:  # Intra-cluster configuration

                    _c1 = relation[0]
                    if _c1 not in clusters:
                        print "ERROR conf file: cluster " + _c1 + " has not been declared in the topo section"
                        sys.exit(2)
                    
                    clusters[_c1].bdw = str(float(options[0])*1000) # Conf file bandwidth in Mbits/s
                    clusters[_c1].lat = options[1]      # Conf file latency in ms

                elif len(relation) == 2:

                    _c1 = relation[0]
                    _c2 = relation[1]
                    if _c1 not in clusters or _c2 not in clusters:
                        print "ERROR conf file: cluster " + _c1 + " or " + _c2 +"  has not been declared in the topo section"
                        sys.exit(2)

                    if not clusters[_c2].adj.has_key(_c1):
                        print "ERROR conf file: cluster " + _c1 + " not declared as one of the "+ _c2 + "'s adjacents"
                        sys.exit(2)
                    if not clusters[_c1].adj.has_key(_c2):
                        print "ERROR conf file: cluster " + _c2 + " not declared as one of the "+ _c1 + "'s adjacents"
                        sys.exit(2)

                    clusters[_c1].adj[_c2] = (str(float(options[0])*1000), options[1]) # tc accepts values in kbits
                    clusters[_c2].adj[_c1] = (str(float(options[0])*1000), options[1])
                
                    # TODO: Check all nodes if they exist, correct position, address, ...
    return clusters, nodes, mod


def which_router_to_this_neighbor(clusters, nodes, c1c, c2c):
    """ Determines which router to use in c1 to reach c2 """

    c1 = clusters[c1c]
    c2 = clusters[c2c]

    if len(c1.routers) == 0:
        print c1.name + "has no routers"
        return ""
    elif len(c1.routers) == 1: # If c1 has only one router (most of the cases)
        return c1.routers[0]
    else:                     # If not, we have to choose the good one
        for r in c1.routers:
            for a in nodes[r].adj:
                if nodes[a].incluster == c2c:
                    return r
    return ""                           # If no router found, error



class rip:
    """ Implements a virtual RIP (Routing Information Protocol) protocol """

    def __init__(self, clusters, nodes):
        """ Initialize the dictioary containing the RIP info for the grid """

        self.clusters = clusters
        self.nodes = nodes        
        self.table = self.build_rip()


    def build_rip(self):
        """ Returns a dictionary containing the RIP infos for all the clusters """

        rip = {}
        
        # Initialization of the entries for each cluster
        for k, c in self.clusters.iteritems():
            rip[k] = self.add_rip_entry(c)

        # Filling the RIP dictionary for each entry
        for k in self.clusters.keys(): # For each entry

            count = len(rip[k])

            while count + 1 < len(self.clusters): # Continue until the number of links for this entry corresponds to the number of clusters in the grid minus 1
                
                for k2 in rip[k].keys(): # Use the entries already existing in k to determine the shortest ways

                    if k2 == k:        # Does not have to happen
                        pass
                    else:
                        rip[k] = self.update_rip_entry(rip, k, k2)

                count = len(rip[k])

        return rip
    

    def add_rip_entry(self, c1):
        """ Initialize an entry corresponding to k1 in the rip dictionary """

        entry = {}                      # Each entry corresponding to one cluster is a dictionary itself

        for c in c1.adj:
            router = which_router_to_this_neighbor(self.clusters, self.nodes, c1.name, c)
            if router == "":
                print "Error in determination of the router from %s\n((%s))\n to %s" % (c1.name, c1, c)
            else:
                entry[c] = (c, router, 1)

        return entry
    

    def update_rip_entry(self, rip, k1, k2):
        """ Update the entry 'k1' of the RIP table 'rip' using the value of the entry k2 """

        for k, desc in rip[k2].iteritems():

            if k == k1:                 # This entry of c2 is c1
                pass;
            else:
                if k in rip[k1].keys(): # If the element in the second entry al ready exist, then check if need to update this entry or not
                    if (rip[k1][k2][2] + desc[2]) < rip[k1][k][2]:
                        rip[k1][k] = (k2, rip[k1][k2][1], rip[k1][k2][2] + desc[2])

                else:                   # Else, insert this entry and update the metric
                    rip[k1][k] = (k2, rip[k1][k2][1], rip[k1][k2][2] + desc[2])

        return rip[k1]



def print_wreka(nodes, clusters):
    """ Print the equivalent wrekagrid configuration file
    """

    seed = "1"
    cpu = "700"
    bpout = "100"
    bpin = "100"
    lat = "0"
    user = "basil"
    mem = "500000"

    block = "SEED : "+seed+"\n"+\
    "CPU : ["+cpu+";0]\n"+\
    "BPOUT : ["+bpout+";0]\n"+\
    "BPIN : ["+bpin+";0]\n"+\
    "LAT : ["+lat+";0]\n"+\
    "USER : "+user+"\n"+\
    "MEM : ["+mem+";0]\n"+\
    "}\n"

    tnode = Template("$name : [${address}-${address}]{\n"+block)

    tinter = Template("!INTER : [${node1};${node2}] [${bdw};0] [${bdw};0] [${lat};0] -1")

    # First, declaration of the "clusters" (for us, just nodes)
    for _node in nodes.itervalues():
        print tnode.substitute(name = _node.name, address = _node.address)
    
    # Second, links inside clusters
    for _cluster in clusters.itervalues():
        lat = _cluster.lat
        bdw = int(float(_cluster.bdw)/1000)
        for _node1 in _cluster.nodes:
            for _node2 in _cluster.nodes[_cluster.nodes.index(_node1):]:
                if (_node2 != _node1):
                    print tinter.substitute(node1 = _node1, node2 = _node2, bdw = bdw, lat = lat)
        print 


    # Third, links from the simple nodes in one cluster, to all the others nodes in the world
    for _cluster1 in clusters.itervalues():
        for _cluster2 in clusters.itervalues():
            if (_cluster1 != _cluster2):
                lat = _cluster1.lat
                bdw = int(float(_cluster1.bdw)/1000)
                for _node1 in _cluster1.nodes:
                    for _node2 in _cluster2.nodes:
                        if (nodes[_node1].adj == []):
                            print tinter.substitute(node1 = _node1, node2 = _node2, bdw = bdw, lat = lat)
        print

    rip_table = rip(clusters, nodes)
    # Finally, deal with the routers in each cluster
    for _cluster1 in clusters.itervalues():
        for _cluster2 in clusters.itervalues():
            if (_cluster1 != _cluster2):
                for _node1 in _cluster1.nodes:
                    for _node2 in _cluster2.nodes:
                        if (nodes[_node1].adj != []):
                            gate = nodes[_node1].find_gw(nodes[_node2], nodes, clusters, rip_table.table)
                            if gate == "":
                                lat = _cluster1.adj[nodes[_node2].incluster][1]
                                bdw = int(float(_cluster1.adj[nodes[_node2].incluster][0])/1000)
                            else:
                                if gate in _cluster1.nodes:
                                    lat = cluster1.lat
                                    bdw = int(float(_cluster1.nodes)/1000)
                                else:
                                    lat = _cluster1.adj[nodes[gate].incluster][1]
                                    bdw = int(float(_cluster1.adj[nodes[gate].incluster][0])/1000)
                            print tinter.substitute(node1 = _node1, node2 = _node2, bdw = bdw, lat = lat)
        print
    
def print_summary(nodes, conf_file = "", filename = ""):
    """ Print summary of the orders sent to the nodes """

    if filename == "":

        print "# Vlan summary for " + conf_file 
        print "# " + str(datetime.now()) + "\n"

        for k, n in nodes.iteritems():
            print "\n# Add topology configuration for %s (%s)" % (n.address, k)
            print n.add_topo
            print "\n# Add network configuration for %s (%s)" % (n.address, k)
            print n.add_net
            print "# Delete topology configuration for %s (%s)" % (n.address, k)
            print n.del_topo
            print "\n# Delete network configuration for %s (%s)" % (n.address, k)
            print n.del_net
        
    else:
        f = file(filename, "w")

        f.write("# Vlan summary for %s \n" % conf_file)
        f.write( "# " + str(datetime.now()) + "\n\n")

        f.write("# Configuration file -------------------------------------")
        conf = file(conf_file,"r")
        f.write(conf.read())
    
        f.write("\n\n# Corresponding orders ----------------------------------- \n\n ")
        for k, n in nodes.iteritems():
            f.write("\n# Add topology configuration for %s (%s)\n" % (n.address, k))
            f.writelines(n.add_topo)
            f.write("\n# Del topology configuration for %s (%s)\n" % (n.address, k))
            f.writelines(n.del_topo)
            f.write("\n# Add network configuration for %s (%s)\n" % (n.address, k))
            f.writelines(n.add_net)
            f.write("\n# Del network configuration for %s (%s)\n" % (n.address, k))
            f.writelines(n.del_net)

        f.close()
        conf.close()

    
def main():
    """ Main function """

    ifn = "eth0"
    
    suf_conf = "conf"
    suf_sum = "sum"
    suf_log = "log"
    conf_file = "./python/vlan_cano28.conf"
    switch_mode = 1                          # 1 means delete config, 0 means add
    switch_sum = 0                           # 1 means print sum and log, 0 means just .sum, .log files
    virtue = 0                               # If 1, just print the summary, send nothing (fake mode)
    fl_type = 2                              # 0 means only topology, 1 means only network, 2 means both
    fl_debug = 0                             # Debug: 0=no debug, 1=debug
    fl_wreka = 0                             # Wrekagrid input file: 0=dont create it, 1=print it
    
    # Parse command line options
    try:
        opts, args = getopt.getopt(sys.argv[1:], "fhadvc:w", ["help", "debug", "add_topo", "del_conf", "conf_conf", "silent", "fake", "wrekagrid"])
    except getopt.error, msg:
        print msg
        print "For help, use --help"
        sys.exit(2)

    # Process options
    for o, a in opts:

        if o in ("-h", "--help"):
            print __doc__
            sys.exit(0)
        elif o in ("-a", "--add_conf"):
            switch_mode = 0
        elif o in ("-d", "--del_conf"):
            switch_mode = 1
        elif o in ("-c", "--conf_file"):
            conf_file = a
        elif o in ("-v","--verbose"):
            switch_sum = 1
        elif o in ("-w", "--wrekagrid"):
            virtue = 1
            fl_wreka = 1
        elif o in ("--debug"):
            switch_sum = 1
            fl_debug = 1
        elif o in ("-f", "--fake"):
            virtue = 1

    # Run program
    conf = file(conf_file, "r")
    clusters, nodes, fl_type = conf_parse(conf)
    
    if fl_debug:
        print "Conf file "+conf_file
        pprint.pprint(clusters)
        pprint.pprint(nodes)

    sum_file = os.path.basename(conf_file).split(".")[0] + "." + suf_sum
    log_file = os.path.basename(conf_file).split(".")[0] + "." + suf_log
    
    for k, _node in nodes.iteritems():
        _node.ifn = ifn
        _node.build_topo(nodes, clusters)
        _node.build_net(nodes, clusters)

    if fl_wreka == 1:
        print_wreka(nodes, clusters)

    sum = file(sum_file, "w")
        
    if switch_sum == 1:
        print_summary(nodes)
    print_summary(nodes, conf_file, sum_file)
    sum.close()

    if virtue == 0:

        log = file(log_file, "w")
        log.write("# Vlan log for %s \n" % conf_file)
        log.write( "# " + str(datetime.now()) + "\n\n")

        for k, _node in nodes.iteritems():
            _node.send_order(log, switch_mode, fl_type)
                
        log.close()
        
    print fl_type
if __name__ == "__main__":
    main()
